import ProjectEntity from '@main/db/entity/ProjectEntity'
import { Injectable } from '@nestjs/common'
import { Like, Repository } from 'typeorm'
import { InjectRepository } from '@nestjs/typeorm'
import PageResult from '@main/bo/PageResult'
import ApiEntity from '@main/db/entity/ApiEntity'
import { match } from 'path-to-regexp'
import Mock from 'mockjs'
import { MockReqType } from './typing'
import { Response } from 'express'
import { VM } from 'vm2'
import { createReadStream } from 'node:original-fs'
import { ApiResp } from '@main/bo/ApiResp'
import { getMockDownloadFilePath } from '@main/util/CommUtil'
import DataRepeatException from '@main/exception/DataRepeatException'
import { sendBizExp } from '@main/util/RespUtil'
import BizException from '@main/exception/BizException'

function sleep(time: number) {
  return new Promise<void>(reslove => {
    setTimeout(() => {
      reslove()
    }, time)
  })
}
@Injectable()
export class ApiService {
  constructor(
    @InjectRepository(ApiEntity)
    private repository: Repository<ApiEntity>,
    @InjectRepository(ProjectEntity)
    private projectRepository: Repository<ProjectEntity>
  ) {}

  async page(projectId: number, current: number, size: number, kw: string) {
    const queryBuilder = this.repository.createQueryBuilder().andWhere('project_id = :projectId', { projectId })
    if (kw) {
      queryBuilder.andWhere('(name like :kw or uri like :kw)', { kw: `%${kw}%` })
    }
    const ret = await queryBuilder
      .skip((current - 1) * size)
      .take(size)
      .orderBy('id', 'DESC')
      .getManyAndCount()
    return PageResult.builder(current, size, ret[0], ret[1])
  }

  async add(entity: ApiEntity): Promise<number> {
    this.validateUri(entity.uri)

    const count = await this.repository
      .createQueryBuilder()
      .andWhere('project_id = :projectId', { projectId: entity.projectId })
      .andWhere('uri = :uri', { uri: entity.uri })
      .getCount()
    if (count != 0) {
      throw new DataRepeatException('请求URI重复!')
    }
    const ret = await this.repository.createQueryBuilder().insert().into(ApiEntity).values([entity]).execute()
    return ret.raw
  }

  async copyAdd(id: number): Promise<number> {
    let baseEntity = await this.repository.createQueryBuilder().andWhere(`id = :id`, { id }).getOne()
    baseEntity.uri = baseEntity.uri + '-' + new Date().getTime()
    baseEntity.name = baseEntity.name + ':复制'
    delete baseEntity.id
    const ret = await this.repository.createQueryBuilder().insert().into(ApiEntity).values([baseEntity]).execute()
    return ret.raw
  }

  validateUri(uri: string) {
    try {
      match(uri, { decode: decodeURIComponent })
    } catch (e) {
      throw new BizException('uri中包含未被转义的特殊字符串. 如: +, *, ?')
    }
  }

  async modify(entity: ApiEntity) {
    this.validateUri(entity.uri)
    const tmp = await this.repository
      .createQueryBuilder()
      .andWhere('project_id = :projectId', { projectId: entity.projectId })
      .andWhere('uri = :uri', { uri: entity.uri })
      .getOne()
    if (tmp && tmp.id !== entity.id) {
      throw new DataRepeatException('请求URI重复!')
    }
    const ret = await this.repository.createQueryBuilder().update(ApiEntity).set(entity).whereInIds(entity.id).execute()
    return ret.affected
  }

  async remove(ids: number[]) {
    if (!ids || ids.length === 0) return
    await this.repository.createQueryBuilder().delete().from(ApiEntity).whereInIds(ids).execute()
  }

  async removeByProjectIds(projectIds: number[]) {
    if (!projectIds || projectIds.length === 0) return
    await this.repository
      .createQueryBuilder()
      .delete()
      .from(ApiEntity)
      .andWhere('project_id in (:...projectIds)', { projectIds })
      .execute()
  }

  // 拿了Response对象之后，这个方法中的异常，无法再被全局异常过滤器处理，只能手动处理
  async executeMockApi(projectFlag: string, mockReq: MockReqType, resp: Response) {
    const project = await this.projectRepository
      .createQueryBuilder()
      .where('flag = :projectFlag', { projectFlag })
      .getOne()

    if (!project) {
      sendBizExp(resp, '未匹配到mock项目')
      return
    }

    if (!project.enable) {
      sendBizExp(resp, `【 ${project.name} 】项目，已被禁用`)
      return
    }

    const apiList = await this.repository
      .createQueryBuilder()
      .andWhere('project_id = :projectId', { projectId: project.id })
      .andWhere('enable = :enable', { enable: 1 })
      .orderBy('id', 'DESC')
      .getMany()
    let machedApi: ApiEntity
    for (const api of apiList) {
      if (api.method !== mockReq.method) {
        continue
      }
      const mm = match(api.uri, { decode: decodeURIComponent })
      const matchObj = mm(mockReq.path)
      if (matchObj) {
        mockReq.params = matchObj.params as Record<string, unknown>
        machedApi = api
        break
      }
    }
    if (!machedApi) {
      sendBizExp(
        resp,
        `根据请求URL信息[ ${mockReq.method}: ${mockReq.fullUrl} ]，在 【 ${project.name} 】项目中, 未匹配到对应的 mock api`
      )
      return
    }
    let httpStatusCode = 200
    let httpContent = null
    let respJson = null
    let delayTime = 0
    let fileType: string = null
    let fileName: string = null
    // 返回json
    function sendJson(any: unknown) {
      respJson = any
    }
    // 修改http状态码
    function sendHttpStatus(code: number, content?: unknown) {
      httpStatusCode = code
      if (content) {
        httpContent = content
      }
    }
    function delay(time: number) {
      delayTime = time
    }
    // 实现文件下载
    function fileDownload(_fileType: string, _fileName?: string) {
      fileType = _fileType
      fileName = _fileName
    }
    const vm = new VM({
      timeout: 5000,
      // allowAsync: true,
      sandbox: {
        Mock: Mock,
        mockReq,
        mockResp: {
          sendHttpStatus,
          sendJson,
          fileDownload,
          delay,
        },
      },
    })
    vm.run(machedApi.mockMethodContent)

    if (delayTime > 0) {
      let realDelayTime = delayTime <= 10000 ? delayTime : 10000
      await sleep(realDelayTime)
    }
    if (httpStatusCode !== 200) {
      console.log('httpStatusCode', httpStatusCode)
      resp.status(httpStatusCode)
      if (httpContent) {
        resp.send(httpContent)
      } else {
        resp.send('')
      }
    } else if (respJson) {
      resp.json(respJson)
    } else if (fileType) {
      if (['word', 'txt', 'execel', 'png', 'jpg', 'zip', 'pdf'].indexOf(fileType) < 0) {
        resp.json(ApiResp.error(500, `暂不支持导出 ${fileType} 类型的文件`))
        return
      }
      const filePath = getMockDownloadFilePath(fileType)
      console.log('文件路径:', filePath)
      // 设置响应头，指示这是一个需要下载的文件，而不是直接在浏览器中打开
      if (fileName) {
        // https://blog.csdn.net/qq_41996454/article/details/126018683
        resp.setHeader('Access-Control-Expose-Headers', 'Content-Disposition')
        resp.setHeader('Content-Disposition', `attachment; filename=${encodeURIComponent(fileName)}`)
      }
      resp.setHeader('Content-Type', 'application/octet-stream')

      // 创建可读流并管道到响应对象，实现文件下载
      const fileStream = createReadStream(filePath)
      fileStream.pipe(resp)
    } else {
      resp.status(202)
      resp.send('')
    }
  }
}
